#/usr/bin/python
#go.py: handles the game of Go being played.

import board
import sys

#default rules + settings of the game
DEFAULT_KOMI = 6.5
DEFAULT_SIZE = 19
DEFAULT_HANDICAP = 0
DEFAULT_BPLAYER_NAME = "Black"
DEFAULT_WPLAYER_NAME = "White"
BLACK_STARTS = True
#the tiles on the board
BLANK = 0
BLACK = 1
WHITE = 2
#string representations of those tiles
COLOR_STRINGS = {BLANK:"Blank", BLACK:"Black", WHITE:"White"}

class GoGame(object):
	komi = 0
	handicap = 0
	bplayer_name = ""
	wplayer_name = ""
	current_player = int()
	vocal = bool()
	resigned_player = None
	
	def __init__(self, filename="", vocal=False):
		"""Initializes a new game of Go."""
		self.vocal = vocal
		if filename:
			try:
				self.load(filename)
			except IOError:
				print("Unable to read file: %s. Does this file exist and is it \
readable?" % filename)
			except ValueError, BoardReadException:
				print("Unable to get all required values from file. Did you \
 follow the file format specification(see GBD-README.txt)?")
						
		else:
			self.request_game_settings()
			self.current_player = BLACK if BLACK_STARTS else WHITE

	def request_game_settings(self):
		"""Asks the user for the settings of the game."""
		size = get(int, "What should the board size be? (default is 19): ", 
			DEFAULT_SIZE)
		self.board = board.GoBoard(size, vocal=self.vocal)
		self.komi = get(float, "What should the komi(White handicap) be \
set at? (default is 6.5): ", DEFAULT_KOMI)
		self.handicap = get(int, "How many handicap stones should Black be \
given? (default is 0): ", DEFAULT_HANDICAP)
		self.bplayer_name = get(str, "What is the Black player's name? \
(default is Black): ", DEFAULT_BPLAYER_NAME)
		self.wplayer_name = get(str, "What is the White player's name? \
(default is White): ", DEFAULT_WPLAYER_NAME)

	def play(self):
		"""Plays the game."""
		game_done = False
		while not game_done:
			#display board, declare whose turn it is
			print(self.board)
			print("%s(%s)'s move('resign' to resign or 'end' to finish the game):" %
				(self.bplayer_name if self.current_player == BLACK else
				self.wplayer_name, COLOR_STRINGS.get(self.current_player)))

			move = self.request_move()
			if not move:
				#either game ended or resigned
				if self.resigned_player:
					#a player has resigned, declare winner
					print("%s has resigned from the game." % \
						COLOR_STRINGS.get(self.resigned_player))
					winner = WHITE if self.resigned_player == BLACK else BLACK
					self.declare_winner(winner)
				else:
					#calculate scores and declare winner
					winner = self.get_winner()
					self.declare_winner(winner)
				game_done = True
			else:
				#make the move if it's legal
				try:
					self.board.make_move(self.current_player, move)
				except board.IllegalMoveException as e:
					print("Illegal move at %s: %s" % (str(e.coordinates),
						e.reason))			
				else:
					self.current_player = \
						WHITE if self.current_player == BLACK else BLACK

	def load(self, filename):
		"""Opens the file indicated by filename and tries to load a game state
		from it. raises a StateReadException if the file indicated by filename 
		does not follow the .gbd specification(see GBD-README.txt)."""
		with open(filename, "r") as f:
			#get the player names from the file's first line
			name_line = f.readline()
			self.bplayer_name, self.wplayer_name = name_line.split()
			
			#get the game settings from the file's second line
			settings_line = f.readline()
			settings = settings_line.split()
			size = int(settings[0])
			self.komi = float(settings[1])
			self.handicap = int(settings[2])
			self.current_player = BLACK if settings[3].lower() == "black" \
				else WHITE

			#read in the board data from the file
			self.board = board.GoBoard(size, src_file=f, vocal=self.vocal)
	
	def request_move(self):
		x = get(int, "column of the move(columns are 1-19): ",
			lower_bound=1, 	upper_bound=self.board.size, 
			cancel_options=["resign", "end"])
		#did they resign?
		if (str(x).lower() == "resign"):
			self.resigned_player = self.current_player
			return

		y = get(int, "row of the move(rows are 1-19): ", 
			lower_bound=1, upper_bound=self.board.size, 
			cancel_options=["resign", "end"])

		#now did they resign?
		if (str(y).lower() == "resign"):
			self.resigned_player = self.current_player
			return
		
		#since the board array coordinates start at 0, subtract 1.
		return (y-1, x-1)
	
	def get_winner():
		"""Figures out who the winner of the game is. Declares White the winner
		in the situation of a draw due to Black having the first move
		advantage."""
		print("Please count the territory for Black and White.")
		black_score = get(float, "Black's score?: ", lower_bound=0.0)
		white_score = get(float, "White's score?: ", lower_bound=0.0)
		white_score += self.komi
		if white_score >= black_score:
			return WHITE
		else:
			return BLACK

	def declare_winner(self, color):
		"""Declares that the winner of the game was color."""
		print("%s(%s) has won the game!" % \
			self.bplayer_name if color == BLACK else self.wplayer_name,
			COLOR_STRINGS.get(color))
	
def get(type_, prompt="", default=None, lower_bound=None, upper_bound=None,
	cancel_options=None):
	"""Gets a value of type type_. Requests for value using prompt, defaults
	to the value default if no response is given(which should generally be of 
	the same type as type_)"""
	while True:
		try:
			response = raw_input(prompt)
			if not response:
				if default != None: return default
				else:
					print("Invalid input: you didn't put anything.")
			else:
				#did the user cancel? if so, get out of here.
				if cancel_options and response in cancel_options:
					return response
				#cool, we got a response. Let's convert it to the type we want,
				#or get angry if it's not convertable.
				response = type_(response)

				#cool, this is a valid response. Check that the value fits 
				#within the given boundaries
				in_bounds = True
				if lower_bound:
					if not response >= lower_bound:
						print("Invalid input: value is too low.")
						in_bounds = False
				if upper_bound:
					if not response <= upper_bound:
						print("Invalid input: value is too high.")
						in_bounds = False

				if in_bounds:
					return response
		except ValueError:
			print("Invalid input.")

def confirm(prompt):
	"""Asks prompt to the user and waits for the response yes or no. Returns
	True if yes, false otherwise."""
	response = ""
	yes = ["yes", "y"]
	no = ["no", "n"]
	valid = yes + no
	while True:
		response = raw_input(prompt + "(y/n)")
		if response in any(valid):
			return (response in yes)
		else:
			print "Invalid response(choices are", valid, ")"

def last(L):
	"""Gets the last element of the list L."""
	return L[len(L) - 1]

if __name__ == "__main__":
	games_to_load = sys.argv[1:]
	if games_to_load:
		for game_name in games_to_load:
			game = GoGame(game_name, vocal=True)
			game.play()
	else:
		game = GoGame(vocal=True)
		game.play()
